﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WebEye.Controls.WinForms.WebCameraControl;

namespace BeerScanatronUI
{
    public partial class TakePicture : Form
    {
        private string _cameraName = null;
        private float _deltaX;
        private float _deltaY;

        /// <summary>
        /// Created in timer when camera frame is rendered.  It is copy cropped and scaled
        /// image without graticules.  This is a buffer that will be copied to StillImage
        /// and imported by caller if desired.
        /// </summary>
        private Image _withoutGraticuleImage;

        /// <summary>
        /// Captured image from view finder.  Initially set to picture sent by caller.
        /// </summary>
        public Image StillImage
        {
            get { return (Image)pboxStillImage.Image.Clone(); }
            set { pboxStillImage.Image = (Image)value.Clone(); }
        }

        /// <summary>
        /// Constructor.  Pass in name of camera and an initial image for the StillImage.
        /// </summary>
        /// <param name="cameraName"></param>
        /// <param name="stillImage"></param>
        public TakePicture(string cameraName, Image stillImage)
        {
            _cameraName = cameraName;
            InitializeComponent();
            StillImage = new Bitmap(stillImage);
            this.BackColor = Consts.Theme.BackColor;

            _deltaX = 0.5F;
            _deltaY = 0.5F;
        }

        /// <summary>
        /// Start camera source renderer.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TakePicture_Load(object sender, EventArgs e)
        {
            try
            {
                // Get cameras and find name that matches passed camera.
                bool cameraError = true;
                if (!String.IsNullOrWhiteSpace(_cameraName))
                {
                    WebCameraId camera = webCam.GetVideoCaptureDevices().Where(t => t.Name == _cameraName).FirstOrDefault();

                    if (camera != null)
                    {
                        webCam.StartCapture(camera);
                        webCam.MouseWheel += WebCam_MouseWheel;
                        pboxGraticuleImage.MouseWheel += WebCam_MouseWheel;
                        cameraError = false;
                        timer1.Enabled = true;
                    }
                }

                if (cameraError)
                {
                    MessageBox.Show("Camera can be found or you have not selected one.  Select one from menu on main screen.",
                        "Bad camera", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error starting camera: " + ex.Message,
                    "Bad camera", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }

        /// <summary>
        /// Mouse wheel scrolls on the WebCam picture will change zoom factor.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WebCam_MouseWheel(object sender, MouseEventArgs e)
        {
            int v = e.Delta / SystemInformation.MouseWheelScrollDelta;
            v = scrollScale.Value - v;
            if (v < scrollScale.Minimum)
            {
                v = scrollScale.Minimum;
            }
            else if (v > scrollScale.Maximum)
            {
                v = scrollScale.Maximum;
            }

            scrollScale.Value = v;
        }

        /// <summary>
        /// Use mouse location information to set center of selected image.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void webCam_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _deltaX = ((float)e.X + 1) / (float)webCam.Width;
                _deltaY = ((float)e.Y + 1) / (float)webCam.Height;
            }

            Trace.WriteLine(String.Format("X: {0}  Y: {1}", _deltaX, _deltaY));
        }

        /// <summary>
        /// Capture new image and update image with graticule (pboxGraticuleImage) and another that
        /// does not have the graticule (_withoutGraticuleImage).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            bool gotImage = false;

            // Get image. If any error occurs, ignore it and render on next timer tick.
            if (webCam.IsCapturing)
            {
                try
                {
                    // Source image: 640 x 480 (typical)
                    Bitmap src = webCam.GetCurrentImage();
                    gotImage = true;
                }
                catch (Exception ex)
                {
                    // Ignore exception.
                    Trace.WriteLine(ex.Message);
                }
            }

            // Render image if it exists.
            if (webCam.IsCapturing && gotImage)
            {
                // Flip vertical scroll bar so going up increase zoom.
                //  zoom = [1.0 .. n]
                int z = scrollScale.InvertScrollValue();
                float zoom = (float)z / (float)scrollScale.Minimum;

                // Source image: 640 x 480 (typical)
                Bitmap src = webCam.GetCurrentImage();
                float srcWidth = src.Width;
                float srcHeight = src.Height;

                // Final image: 120 x 120 with same resolution.
                Bitmap dest = new Bitmap(120, 120);
                dest.SetResolution(src.HorizontalResolution, src.VerticalResolution);

                // Get graphics context to allow matrix transforms.
                using (Graphics gr = Graphics.FromImage(dest))
                {
                    // Make final image pretty.
                    gr.SmoothingMode = SmoothingMode.HighQuality;
                    gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    gr.PixelOffsetMode = PixelOffsetMode.HighQuality;

                    // Determine translation to origin for current zoom factor.
                    //  This will also create parameters for a normalized image.
                    float offsetX = (((float)src.Height / zoom) - src.Width) / 2;
                    float offsetY = (((float)src.Height / zoom) - src.Height) / 2;

                    Matrix matrix = new Matrix();
                    matrix.Translate(offsetX, offsetY);

                    float scale = dest.Height / (float)src.Height * zoom;
                    matrix.Scale(scale, scale, MatrixOrder.Append);
                    gr.Transform = matrix;

                    // Apply brightness adjustment.
                    float brighness = (float)scrollBrightness.InvertScrollValue() / 100F;
                    ColorMatrix cm = new ColorMatrix(new float[][]
                    {
                        new float[] { 1F, 0, 0, 0, 0},
                        new float[] { 0, 1F, 0, 0, 0},
                        new float[] { 0, 0, 1F, 0, 0},
                        new float[] { 0, 0, 0, 1F, 0},
                        new float[] { brighness, brighness, brighness, 0, 1F },
                    });

                    ImageAttributes attr = new ImageAttributes();
                    attr.SetColorMatrix(cm);

                    gr.DrawImage(src,
                        new Rectangle((int)((0.5F - _deltaX) * srcWidth), // - (int)(20F * zoom),
                            (int)((0.5F - _deltaY) * srcWidth),
                            src.Width, src.Height), 0, 0,
                        src.Width, src.Height, GraphicsUnit.Pixel, attr);

                    float halfSrcWidth = src.Width / 2;
                    float halfSrcHeight = src.Height / 2;

                    // If image is not entirely filling target, don't allow saving a picture.
                    btnCaptureImage.Enabled = (dest.GetPixel(0, 0).IsValidColor()
                        && dest.GetPixel(0, 119).IsValidColor()
                        && dest.GetPixel(119, 0).IsValidColor()
                        && dest.GetPixel(119, 119).IsValidColor());

                    // Save image without graticule.
                    _withoutGraticuleImage = (Bitmap)dest.Clone();

                    // Draw graticule.
                    float gratOffset = src.Height / 4F / zoom;
                    gr.DrawLine(Pens.Red, new PointF(halfSrcWidth, 0), new PointF(halfSrcWidth, halfSrcHeight - gratOffset));
                    gr.DrawLine(Pens.Red, new PointF(halfSrcWidth, halfSrcHeight + gratOffset), new PointF(halfSrcWidth, src.Height));

                    gr.DrawLine(Pens.Red, new PointF(0, halfSrcHeight), new PointF(halfSrcWidth - gratOffset, halfSrcHeight));
                    gr.DrawLine(Pens.Red, new PointF(halfSrcWidth + gratOffset, halfSrcHeight), new PointF(src.Width, halfSrcHeight));

                    pboxGraticuleImage.Image = dest;
                }
            }
        }

        /// <summary>
        /// User wants to select this image.  Copy the non-graticule image to StillImage.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCaptureImage_Click(object sender, EventArgs e)
        {
            StillImage = _withoutGraticuleImage;
        }
    }
}
